Table of Contents

Relational Data with dplyr

Typically we will be using more than one table of data for an analysis. Multiple tables of data that relate to each other are called relational data.

Most common place to find relational data is relational databases. If you have used SQL for accessing a database, you will find most of the concepts and operations defined here very similar.

To work with relational data you need verbs (functions) that work with pairs of tables. There are three families of verbs designed to work with relational data:

dplyr provides a variety of two-table verbs that we will introduce them here. But first let’s look at the tables that we will be using here.

nycflights13 package

We will use the nycflights13 package to learn about relational data. nycflights13 contains four dataframes that are related to the flights, the dataframe that we used previously.

The drawing below shows how this relationship is defined:

print(airlines)
print(airports)
print(planes)
print(weather)

For nycflights13:

  • flights connects to planes via a single variable, tailnum.
  • flights connects to airlines through the carrier variable.
  • flights connects to airports in two ways: via the origin and dest variables.
  • flights connects to weather via origin (the location), year, month, day and hour (the time).

Keys

A key is a variable (or set of variables) that uniquely identifies an observation. In simple cases, a single variable is sufficient to identify an observation. For example, each plane is uniquely identified by its tailnum. In other cases, multiple variables may be needed. For example, to identify an observation in weather you need five variables: year, month, day, hour, and origin.

There are two types of keys:

  • A primary key uniquely identifies an observation in its own table. For example, planes$tailnum is a primary key because it uniquely identifies each plane in the planes table.
  • A foreign key uniquely identifies an observation in another table. For example, the flights$tailnum is a foreign key because it appears in the flights table where it matches each flight to a unique plane.

A variable can be both a primary key and a foreign key. For example, origin is part of the weather primary key, and is also a foreign key for the airport table.

Once you’ve identified the primary keys in your tables, it’s good practice to verify that they do indeed uniquely identify each observation. One way to do that is to count() the primary keys and look for entries where n is greater than one:

planes %>% 
  count(tailnum) %>% 
  filter(n > 1)

Sometimes a table doesn’t have an explicit primary key: each row is an observation, but no combination of variables reliably identifies it. For example:

weather %>% 
  count(year, month, day, hour, origin) %>% 
  filter(n > 1)

what’s the primary key in the flights table? You might think it would be the date plus the flight or tail number, but neither of those are unique:

flights %>% 
  count(year, month, day, flight) %>% 
  filter(n > 1) %>%
  head
flights %>% 
  count(year, month, day, tailnum) %>% 
  filter(n > 1) %>%
  head

Each flight number can be used more than once a day. With the existing data in flights table we can see the following combination could be the key:

year, month, day, flight, origin, dest, tailnum

flights %>% 
  count(year, month, day, flight, origin, dest, tailnum) %>% 
  filter(n > 1)

We should be cautious when defining the primary key (PK) for a table. It could be that for the existing data a combination of variables would work as a PK but it won’t stand unique for the future data. If a table lacks a primary key, it’s sometimes useful to add one with mutate() and row_number().

flights %>%
  arrange(year, month, day, origin, dest, flight, tailnum) %>%
  mutate(row_n = row_number()) %>%
  select(row_n, everything()) %>%
  print

That makes it easier to match observations if you’ve done some filtering and want to check back in with the original data.

A primary key and the corresponding foreign key in another table form a relation.


Mutating Joins

Joins are used to combine a pair of tables. These are known as mutating joins because we will be adding new variables from another table. This is possible through the foreign key. For instance, we can combine flights dataframe with airlines to get the full names of 2-letter carrier by using a left join:

flights %>%
  left_join(airlines, by = "carrier") %>%
  select(carrier, name, everything()) %>%
  head

We are going to use a visual representation to illustrate different types of joins. Consider these 2 dataframes: x and y

x <- tribble(
  ~key, ~val_x,
     1, "x1",
     2, "x2",
     3, "x3"
)
x
y <- tribble(
  ~key, ~val_y,
     1, "y1",
     2, "y2",
     4, "y3"
)
y

First column with colored numerical values is the key for both tibbles and is used to find the matching records across the tables.

A join is a way of connecting each row in x to zero, one, or more rows in y. The following diagram shows each potential match as an intersection of a pair of lines. For illustration purposes we have switched the order of key and val_x in x:

In an actual join, matches will be indicated with dots. The number of dots = the number of matches = the number of rows in the output.


Inner join

The simplest type of join is the inner join. An inner join matches pairs of observations whenever their keys are equal:

We use by parameter to tell dplyr which variable is the key:

x %>% 
  inner_join(y, by = "key")

Note that unmatched rows are not included in the result. This means that inner joins are usually not appropriate for use in analysis because it’s too easy to lose observations.


Outer joins

An inner join keeps observations that appear in both tables. An outer join keeps observations that appear in at least one of the tables. There are three types of outer joins:

The most commonly used join is the left join: you use this whenever you look up additional data from another table, because it preserves the original observations even when there isn’t a match. The left join should be your default join: use it unless you have a strong reason to prefer one of the others.

Another way to visually represent these joins:

# Left join
x %>% 
  left_join(y, by = "key")
# Right join
x %>% 
  right_join(y, by = "key")
# Full join
x %>% 
  full_join(y, by = "key")

SQL is the inspiration for dplyr’s conventions, so the translation is straightforward:

dplyr SQL
inner_join(x, y, by = “z”) SELECT * FROM x INNER JOIN y USING (z)
left_join(x, y, by = “z”) SELECT * FROM x LEFT OUTER JOIN y USING (z)
right_join(x, y, by = “z”) SELECT * FROM x RIGHT OUTER JOIN y USING (z)
full_join(x, y, by = “z”) SELECT * FROM x FULL OUTER JOIN y USING (z)

Base R also provides join functionality by merge() function, but using dplyr joining is much faster, more consistent and unlike merge() dplyr doesn’t change the order of the rows after the join.

Handling duplicate keys:

x <- tribble(
  ~key, ~val_x,
     1, "x1",
     2, "x2",
     2, "x3",
     3, "x4"
)
y <- tribble(
  ~key, ~val_y,
     1, "y1",
     2, "y2",
     2, "y3",
     3, "y4"
)
left_join(x, y, by = "key")

Defining the key columns

The default, by = NULL, uses all variables that appear in both tables, the so called natural join. For example, the flights and weather tables match on their common variables: year, month, day, hour and origin:

flights %>% 
  left_join(weather) %>%
  print
Joining, by = c("year", "month", "day", "origin", "hour", "time_hour")

We can also specify what key to use:

flights %>% 
  left_join(planes, by = "tailnum") %>%
  print

by = c("a" = "b"). This will match variable a in table x to variable b in table y. The variables from x will be used in the output:

flights %>% 
  left_join(airports, by = c("dest" = "faa")) %>%
  print

Filtering Joins

Filtering joins match observations in the same way as mutating joins, but affect the observations, not the variables. There are two types:

  • semi_join(x, y) keeps all observations in x that have a match in y.
  • anti_join(x, y) drops all observations in x that have a match in y.

Semi-joins are useful for matching filtered summary tables back to the original rows. For example:

# Top 10 most favorite destinations
top_dest <- flights %>%
  count(dest, sort = TRUE) %>%
  head(10)

The following gives all the flight records to the top 10 destinations:

flights %>% 
  semi_join(top_dest) %>%
  print
Joining, by = "dest"

Graphically, a semi-join looks like this:

Only the existence of a match is important; it doesn’t matter which observation is matched. This means that filtering joins never duplicate rows like mutating joins do.

The inverse of a semi-join is an anti-join. An anti-join keeps the rows that don’t have a match:

Anti-joins are useful for diagnosing join mismatches. For example, when connecting flights and planes, you might be interested to know that there are many flights that don’t have a match in planes:

flights %>%
  anti_join(planes, by = "tailnum") %>%
  count(tailnum, sort = TRUE) %>%
  print

Join problems

We have been working with clean data, this is often not the case in real world. Have the following points in mind when joining dataframes to avoid making mistakes:

  1. Start by identifying the variables that form the primary key in each table. You should usually do this based on your understanding of the data, not empirically by looking for a combination of variables that give a unique identifier. If you just look for variables without thinking about what they mean, you might get (un)lucky and find a combination that’s unique in your current data but the relationship might not be true in general.
    • For example, the altitude and longitude uniquely identify each airport, but they are not good identifiers!
  2. Check that none of the variables in the primary key are missing. If a value is missing then it can’t identify an observation!

  3. Check that your foreign keys match primary keys in another table. The best way to do this is with an anti_join(). It’s common for keys not to match because of data entry errors. Fixing these is often a lot of work.

  4. Be aware that simply checking the number of rows before and after the join is not sufficient to ensure that your join has gone smoothly. If you have an inner join with duplicate keys in both tables, you might get unlucky as the number of dropped rows might exactly equal the number of duplicated rows!


Set operations

All these operations work with a complete row, comparing the values of every variable. These expect the x and y inputs to have the same variables, and treat the observations like sets:

  • intersect(x, y): return only observations in both x and y.
  • union(x, y): return unique observations in x and y.
  • setdiff(x, y): return observations in x, but not in y.

Given this simple data:

df1 <- tribble(
  ~x, ~y,
   1,  1,
   2,  1
)
df2 <- tribble(
  ~x, ~y,
   1,  1,
   1,  2
)

The four possibilities are:

intersect(df1, df2)

note that union automatically filters the overlapping records, hence 3 rows!

union(df1, df2)
setdiff(df1, df2)
setdiff(df2, df1)
LS0tCnRpdGxlOiAiUmVsYXRpb25hbCBEYXRhIgpzdWJ0aXRsZTogIkRhdGEgTWFuaXB1bGF0aW9uIC0gUGFydCAyIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciBlY2hvPUZBTFNFfQojIGxvYWRpbmcgbGlicmFyaWVzCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KG55Y2ZsaWdodHMxMykKCiMgbW9kaWZ5aW5nIGNoYXJ0IHNpemUKb3B0aW9ucyhyZXByLnBsb3Qud2lkdGg9NSwgcmVwci5wbG90LmhlaWdodD0zKQpgYGAKCgojIyMgVGFibGUgb2YgQ29udGVudHMKKiBSZWxhdGlvbmFsIERhdGEgd2l0aCBkcGx5cgogICAgKiBLZXlzCiAgICAqIE11dGF0aW5nIEpvaW5zCiAgICAgICAgKiBJbm5lciBqb2luCiAgICAgICAgKiBPdXRlciBqb2lucwogICAgKiBGaWx0ZXJpbmcgam9pbnMKICAgICogSm9pbiBwcm9ibGVtcwogICAgKiBTZXQgb3BlcmF0aW9ucwoKIyMgUmVsYXRpb25hbCBEYXRhIHdpdGggZHBseXIKVHlwaWNhbGx5IHdlIHdpbGwgYmUgdXNpbmcgbW9yZSB0aGFuIG9uZSB0YWJsZSBvZiBkYXRhIGZvciBhbiBhbmFseXNpcy4gTXVsdGlwbGUgdGFibGVzIG9mIGRhdGEgdGhhdCByZWxhdGUgdG8gZWFjaCBvdGhlciBhcmUgY2FsbGVkIHJlbGF0aW9uYWwgZGF0YS4gCgpNb3N0IGNvbW1vbiBwbGFjZSB0byBmaW5kIHJlbGF0aW9uYWwgZGF0YSBpcyByZWxhdGlvbmFsIGRhdGFiYXNlcy4gSWYgeW91IGhhdmUgdXNlZCBTUUwgZm9yIGFjY2Vzc2luZyBhIGRhdGFiYXNlLCB5b3Ugd2lsbCBmaW5kIG1vc3Qgb2YgdGhlIGNvbmNlcHRzIGFuZCBvcGVyYXRpb25zIGRlZmluZWQgaGVyZSB2ZXJ5IHNpbWlsYXIuCgpUbyB3b3JrIHdpdGggcmVsYXRpb25hbCBkYXRhIHlvdSBuZWVkIHZlcmJzIChmdW5jdGlvbnMpIHRoYXQgd29yayB3aXRoIHBhaXJzIG9mIHRhYmxlcy4gVGhlcmUgYXJlIHRocmVlIGZhbWlsaWVzIG9mIHZlcmJzIGRlc2lnbmVkIHRvIHdvcmsgd2l0aCByZWxhdGlvbmFsIGRhdGE6CgoqICoqTXV0YXRpbmcgam9pbnMqKiwgd2hpY2ggYWRkIG5ldyB2YXJpYWJsZXMgdG8gb25lIGRhdGFmcmFtZSBmcm9tIG1hdGNoaW5nIG9ic2VydmF0aW9ucyBpbiBhbm90aGVyLgoqICoqRmlsdGVyaW5nIGpvaW5zKiosIHdoaWNoIGZpbHRlciBvYnNlcnZhdGlvbnMgZnJvbSBvbmUgZGF0YWZyYW1lIGJhc2VkIG9uIHdoZXRoZXIgb3Igbm90IHRoZXkgbWF0Y2ggYW4gb2JzZXJ2YXRpb24gaW4gdGhlIG90aGVyIHRhYmxlLgoqICoqU2V0IG9wZXJhdGlvbnMqKiwgd2hpY2ggdHJlYXQgb2JzZXJ2YXRpb25zIGFzIGlmIHRoZXkgd2VyZSBzZXQgZWxlbWVudHMuCgoqKmRwbHlyKiogcHJvdmlkZXMgYSB2YXJpZXR5IG9mIHR3by10YWJsZSB2ZXJicyB0aGF0IHdlIHdpbGwgaW50cm9kdWNlIHRoZW0gaGVyZS4gQnV0IGZpcnN0IGxldCdzIGxvb2sgYXQgdGhlIHRhYmxlcyB0aGF0IHdlIHdpbGwgYmUgdXNpbmcgaGVyZS4KCiMjIyMgbnljZmxpZ2h0czEzIHBhY2thZ2UKV2Ugd2lsbCB1c2UgdGhlIG55Y2ZsaWdodHMxMyBwYWNrYWdlIHRvIGxlYXJuIGFib3V0IHJlbGF0aW9uYWwgZGF0YS4gbnljZmxpZ2h0czEzIGNvbnRhaW5zIGZvdXIgZGF0YWZyYW1lcyB0aGF0IGFyZSByZWxhdGVkIHRvIHRoZSBgZmxpZ2h0c2AsIHRoZSBkYXRhZnJhbWUgdGhhdCB3ZSB1c2VkIHByZXZpb3VzbHkuCgpUaGUgZHJhd2luZyBiZWxvdyBzaG93cyBob3cgdGhpcyByZWxhdGlvbnNoaXAgaXMgZGVmaW5lZDoKCjxpbWcgc3JjPSIuLi9wbmcvbnljZmxpZ2h0czEzLnBuZyIgd2lkdGg9IjYwMHB4IiBhbGlnbj0iY2VudGVyIj4KCmBgYHtyfQphaXJsaW5lcwpgYGAKCmBgYHtyfQphaXJwb3J0cwpgYGAKCmBgYHtyfQpwbGFuZXMKYGBgCgpgYGB7cn0Kd2VhdGhlcgpgYGAKCkZvciBueWNmbGlnaHRzMTM6CgoqIGBmbGlnaHRzYCBjb25uZWN0cyB0byBgcGxhbmVzYCB2aWEgYSBzaW5nbGUgdmFyaWFibGUsIGB0YWlsbnVtYC4KKiBgZmxpZ2h0c2AgY29ubmVjdHMgdG8gYGFpcmxpbmVzYCB0aHJvdWdoIHRoZSBgY2FycmllcmAgdmFyaWFibGUuCiogYGZsaWdodHNgIGNvbm5lY3RzIHRvIGBhaXJwb3J0c2AgaW4gdHdvIHdheXM6IHZpYSB0aGUgYG9yaWdpbmAgYW5kIGBkZXN0YCB2YXJpYWJsZXMuCiogYGZsaWdodHNgIGNvbm5lY3RzIHRvIGB3ZWF0aGVyYCB2aWEgYG9yaWdpbmAgKHRoZSBsb2NhdGlvbiksIGB5ZWFyYCwgYG1vbnRoYCwgYGRheWAgYW5kIGBob3VyYCAodGhlIHRpbWUpLgoKCi0tLQoKIyMjIEtleXMKQSBrZXkgaXMgYSB2YXJpYWJsZSAob3Igc2V0IG9mIHZhcmlhYmxlcykgdGhhdCB1bmlxdWVseSBpZGVudGlmaWVzIGFuIG9ic2VydmF0aW9uLiBJbiBzaW1wbGUgY2FzZXMsIGEgc2luZ2xlIHZhcmlhYmxlIGlzIHN1ZmZpY2llbnQgdG8gaWRlbnRpZnkgYW4gb2JzZXJ2YXRpb24uIEZvciBleGFtcGxlLCBlYWNoIHBsYW5lIGlzIHVuaXF1ZWx5IGlkZW50aWZpZWQgYnkgaXRzIGB0YWlsbnVtYC4gSW4gb3RoZXIgY2FzZXMsIG11bHRpcGxlIHZhcmlhYmxlcyBtYXkgYmUgbmVlZGVkLiBGb3IgZXhhbXBsZSwgdG8gaWRlbnRpZnkgYW4gb2JzZXJ2YXRpb24gaW4gd2VhdGhlciB5b3UgbmVlZCBmaXZlIHZhcmlhYmxlczogYHllYXJgLCBgbW9udGhgLCBgZGF5YCwgYGhvdXJgLCBhbmQgYG9yaWdpbmAuCgpUaGVyZSBhcmUgdHdvIHR5cGVzIG9mIGtleXM6CgoqIEEgKipwcmltYXJ5IGtleSoqIHVuaXF1ZWx5IGlkZW50aWZpZXMgYW4gb2JzZXJ2YXRpb24gaW4gaXRzIG93biB0YWJsZS4gRm9yIGV4YW1wbGUsIGBwbGFuZXMkdGFpbG51bWAgaXMgYSBwcmltYXJ5IGtleSBiZWNhdXNlIGl0IHVuaXF1ZWx5IGlkZW50aWZpZXMgZWFjaCBwbGFuZSBpbiB0aGUgcGxhbmVzIHRhYmxlLgoqIEEgKipmb3JlaWduIGtleSoqIHVuaXF1ZWx5IGlkZW50aWZpZXMgYW4gb2JzZXJ2YXRpb24gaW4gYW5vdGhlciB0YWJsZS4gRm9yIGV4YW1wbGUsIHRoZSBgZmxpZ2h0cyR0YWlsbnVtYCBpcyBhIGZvcmVpZ24ga2V5IGJlY2F1c2UgaXQgYXBwZWFycyBpbiB0aGUgZmxpZ2h0cyB0YWJsZSB3aGVyZSBpdCBtYXRjaGVzIGVhY2ggZmxpZ2h0IHRvIGEgdW5pcXVlIHBsYW5lLgoKQSB2YXJpYWJsZSBjYW4gYmUgYm90aCBhIHByaW1hcnkga2V5IGFuZCBhIGZvcmVpZ24ga2V5LiBGb3IgZXhhbXBsZSwgYG9yaWdpbmAgaXMgcGFydCBvZiB0aGUgYHdlYXRoZXJgIHByaW1hcnkga2V5LCBhbmQgaXMgYWxzbyBhIGZvcmVpZ24ga2V5IGZvciB0aGUgYGFpcnBvcnRgIHRhYmxlLgoKT25jZSB5b3UndmUgaWRlbnRpZmllZCB0aGUgcHJpbWFyeSBrZXlzIGluIHlvdXIgdGFibGVzLCBpdCdzIGdvb2QgcHJhY3RpY2UgdG8gdmVyaWZ5IHRoYXQgdGhleSBkbyBpbmRlZWQgdW5pcXVlbHkgaWRlbnRpZnkgZWFjaCBvYnNlcnZhdGlvbi4gT25lIHdheSB0byBkbyB0aGF0IGlzIHRvIGBjb3VudCgpYCB0aGUgcHJpbWFyeSBrZXlzIGFuZCBsb29rIGZvciBlbnRyaWVzIHdoZXJlIG4gaXMgZ3JlYXRlciB0aGFuIG9uZToKCmBgYHtyfQpwbGFuZXMgJT4lIAogIGNvdW50KHRhaWxudW0pICU+JSAKICBmaWx0ZXIobiA+IDEpCmBgYAoKU29tZXRpbWVzIGEgdGFibGUgZG9lc24ndCBoYXZlIGFuIGV4cGxpY2l0IHByaW1hcnkga2V5OiBlYWNoIHJvdyBpcyBhbiBvYnNlcnZhdGlvbiwgYnV0IG5vIGNvbWJpbmF0aW9uIG9mIHZhcmlhYmxlcyByZWxpYWJseSBpZGVudGlmaWVzIGl0LiBGb3IgZXhhbXBsZToKCmBgYHtyfQp3ZWF0aGVyICU+JSAKICBjb3VudCh5ZWFyLCBtb250aCwgZGF5LCBob3VyLCBvcmlnaW4pICU+JSAKICBmaWx0ZXIobiA+IDEpCmBgYAoKd2hhdCdzIHRoZSBwcmltYXJ5IGtleSBpbiB0aGUgZmxpZ2h0cyB0YWJsZT8gWW91IG1pZ2h0IHRoaW5rIGl0IHdvdWxkIGJlIHRoZSBkYXRlIHBsdXMgdGhlIGZsaWdodCBvciB0YWlsIG51bWJlciwgYnV0IG5laXRoZXIgb2YgdGhvc2UgYXJlIHVuaXF1ZToKCmBgYHtyfQpmbGlnaHRzICU+JSAKICBjb3VudCh5ZWFyLCBtb250aCwgZGF5LCBmbGlnaHQpICU+JSAKICBmaWx0ZXIobiA+IDEpICU+JQogIGhlYWQKYGBgCgpgYGB7cn0KZmxpZ2h0cyAlPiUgCiAgY291bnQoeWVhciwgbW9udGgsIGRheSwgdGFpbG51bSkgJT4lIAogIGZpbHRlcihuID4gMSkgJT4lCiAgaGVhZApgYGAKCkVhY2ggZmxpZ2h0IG51bWJlciBjYW4gYmUgdXNlZCBtb3JlIHRoYW4gb25jZSBhIGRheS4gV2l0aCB0aGUgZXhpc3RpbmcgZGF0YSBpbiBgZmxpZ2h0c2AgdGFibGUgd2UgY2FuIHNlZSB0aGUgZm9sbG93aW5nIGNvbWJpbmF0aW9uIGNvdWxkIGJlIHRoZSBrZXk6CgpgeWVhciwgbW9udGgsIGRheSwgZmxpZ2h0LCBvcmlnaW4sIGRlc3QsIHRhaWxudW1gCgpgYGB7cn0KZmxpZ2h0cyAlPiUgCiAgY291bnQoeWVhciwgbW9udGgsIGRheSwgZmxpZ2h0LCBvcmlnaW4sIGRlc3QsIHRhaWxudW0pICU+JSAKICBmaWx0ZXIobiA+IDEpCmBgYAoKV2Ugc2hvdWxkIGJlIGNhdXRpb3VzIHdoZW4gZGVmaW5pbmcgdGhlIHByaW1hcnkga2V5IChQSykgZm9yIGEgdGFibGUuIEl0IGNvdWxkIGJlIHRoYXQgZm9yIHRoZSBleGlzdGluZyBkYXRhIGEgY29tYmluYXRpb24gb2YgdmFyaWFibGVzIHdvdWxkIHdvcmsgYXMgYSBQSyBidXQgaXQgd29uJ3Qgc3RhbmQgdW5pcXVlIGZvciB0aGUgZnV0dXJlIGRhdGEuIElmIGEgdGFibGUgbGFja3MgYSBwcmltYXJ5IGtleSwgaXQncyBzb21ldGltZXMgdXNlZnVsIHRvIGFkZCBvbmUgd2l0aCBgbXV0YXRlKClgIGFuZCBgcm93X251bWJlcigpYC4KCmBgYHtyfQpmbGlnaHRzICU+JQogIGFycmFuZ2UoeWVhciwgbW9udGgsIGRheSwgb3JpZ2luLCBkZXN0LCBmbGlnaHQsIHRhaWxudW0pICU+JQogIG11dGF0ZShyb3dfbiA9IHJvd19udW1iZXIoKSkgJT4lCiAgc2VsZWN0KHJvd19uLCBldmVyeXRoaW5nKCkpICU+JQogIHByaW50CmBgYAoKVGhhdCBtYWtlcyBpdCBlYXNpZXIgdG8gbWF0Y2ggb2JzZXJ2YXRpb25zIGlmIHlvdSd2ZSBkb25lIHNvbWUgZmlsdGVyaW5nIGFuZCB3YW50IHRvIGNoZWNrIGJhY2sgaW4gd2l0aCB0aGUgb3JpZ2luYWwgZGF0YS4KCkEgcHJpbWFyeSBrZXkgYW5kIHRoZSBjb3JyZXNwb25kaW5nIGZvcmVpZ24ga2V5IGluIGFub3RoZXIgdGFibGUgZm9ybSBhIHJlbGF0aW9uLgoKLS0tCgojIyMgTXV0YXRpbmcgSm9pbnMKSm9pbnMgYXJlIHVzZWQgdG8gY29tYmluZSBhIHBhaXIgb2YgdGFibGVzLiBUaGVzZSBhcmUga25vd24gYXMgbXV0YXRpbmcgam9pbnMgYmVjYXVzZSB3ZSB3aWxsIGJlIGFkZGluZyBuZXcgdmFyaWFibGVzIGZyb20gYW5vdGhlciB0YWJsZS4gVGhpcyBpcyBwb3NzaWJsZSB0aHJvdWdoIHRoZSBmb3JlaWduIGtleS4gRm9yIGluc3RhbmNlLCB3ZSBjYW4gY29tYmluZSBgZmxpZ2h0c2AgZGF0YWZyYW1lIHdpdGggYGFpcmxpbmVzYCB0byBnZXQgdGhlIGZ1bGwgbmFtZXMgb2YgMi1sZXR0ZXIgYGNhcnJpZXJgIGJ5IHVzaW5nIGEgKipsZWZ0IGpvaW4qKjoKCmBgYHtyfQpmbGlnaHRzICU+JQogIGxlZnRfam9pbihhaXJsaW5lcywgYnkgPSAiY2FycmllciIpICU+JQogIHNlbGVjdChjYXJyaWVyLCBuYW1lLCBldmVyeXRoaW5nKCkpICU+JQogIGhlYWQKYGBgCgpXZSBhcmUgZ29pbmcgdG8gdXNlIGEgdmlzdWFsIHJlcHJlc2VudGF0aW9uIHRvIGlsbHVzdHJhdGUgZGlmZmVyZW50IHR5cGVzIG9mIGpvaW5zLiBDb25zaWRlciB0aGVzZSAyIGRhdGFmcmFtZXM6IGB4YCBhbmQgYHlgCgo8aW1nIHNyYz0iLi4vcG5nL2pvaW4tZGYucG5nIiBhbGlnbj0iY2VudGVyIiB3aWR0aD0iMjAwcHgiPgoKYGBge3J9CnggPC0gdHJpYmJsZSgKICB+a2V5LCB+dmFsX3gsCiAgICAgMSwgIngxIiwKICAgICAyLCAieDIiLAogICAgIDMsICJ4MyIKKQp4CmBgYAoKYGBge3J9CnkgPC0gdHJpYmJsZSgKICB+a2V5LCB+dmFsX3ksCiAgICAgMSwgInkxIiwKICAgICAyLCAieTIiLAogICAgIDQsICJ5MyIKKQp5CmBgYAoKRmlyc3QgY29sdW1uIHdpdGggY29sb3JlZCBudW1lcmljYWwgdmFsdWVzIGlzIHRoZSBrZXkgZm9yIGJvdGggdGliYmxlcyBhbmQgaXMgdXNlZCB0byBmaW5kIHRoZSBtYXRjaGluZyByZWNvcmRzIGFjcm9zcyB0aGUgdGFibGVzLgoKQSBqb2luIGlzIGEgd2F5IG9mIGNvbm5lY3RpbmcgZWFjaCByb3cgaW4geCB0byB6ZXJvLCBvbmUsIG9yIG1vcmUgcm93cyBpbiB5LiBUaGUgZm9sbG93aW5nIGRpYWdyYW0gc2hvd3MgZWFjaCBwb3RlbnRpYWwgbWF0Y2ggYXMgYW4gaW50ZXJzZWN0aW9uIG9mIGEgcGFpciBvZiBsaW5lcy4gRm9yIGlsbHVzdHJhdGlvbiBwdXJwb3NlcyB3ZSBoYXZlIHN3aXRjaGVkIHRoZSBvcmRlciBvZiBga2V5YCBhbmQgYHZhbF94YCBpbiBgeGA6Cgo8aW1nIHNyYz0iLi4vcG5nL2pvaW4tcG90LW1hdGNoLnBuZyIgYWxpZ249ImNlbnRlciIgd2lkdGg9IjI1MHB4Ij4KCkluIGFuIGFjdHVhbCBqb2luLCBtYXRjaGVzIHdpbGwgYmUgaW5kaWNhdGVkIHdpdGggZG90cy4gVGhlIG51bWJlciBvZiBkb3RzID0gdGhlIG51bWJlciBvZiBtYXRjaGVzID0gdGhlIG51bWJlciBvZiByb3dzIGluIHRoZSBvdXRwdXQuCgo8aW1nIHNyYz0iLi4vcG5nL2pvaW4tZG90LnBuZyIgYWxpZ249ImNlbnRlciIgd2lkdGg9IjQ1MHB4Ij4KCi0tLQoKIyMjIElubmVyIGpvaW4KVGhlIHNpbXBsZXN0IHR5cGUgb2Ygam9pbiBpcyB0aGUgaW5uZXIgam9pbi4gQW4gaW5uZXIgam9pbiBtYXRjaGVzIHBhaXJzIG9mIG9ic2VydmF0aW9ucyB3aGVuZXZlciB0aGVpciBrZXlzIGFyZSBlcXVhbDoKCjxpbWcgc3JjPSIuLi9wbmcvam9pbi1pbm5lci5wbmciIGFsaWduPSJjZW50ZXIiIHdpZHRoPSI0NTBweCI+CgpXZSB1c2UgYGJ5YCBwYXJhbWV0ZXIgdG8gdGVsbCBkcGx5ciB3aGljaCB2YXJpYWJsZSBpcyB0aGUga2V5OgoKYGBge3J9CnggJT4lIAogIGlubmVyX2pvaW4oeSwgYnkgPSAia2V5IikKYGBgCgpOb3RlIHRoYXQgdW5tYXRjaGVkIHJvd3MgYXJlIG5vdCBpbmNsdWRlZCBpbiB0aGUgcmVzdWx0LiBUaGlzIG1lYW5zIHRoYXQgaW5uZXIgam9pbnMgYXJlIHVzdWFsbHkgbm90IGFwcHJvcHJpYXRlIGZvciB1c2UgaW4gYW5hbHlzaXMgYmVjYXVzZSBpdCdzIHRvbyBlYXN5IHRvIGxvc2Ugb2JzZXJ2YXRpb25zLgoKLS0tCgojIyBPdXRlciBqb2lucwpBbiBpbm5lciBqb2luIGtlZXBzIG9ic2VydmF0aW9ucyB0aGF0IGFwcGVhciBpbiBib3RoIHRhYmxlcy4gQW4gb3V0ZXIgam9pbiBrZWVwcyBvYnNlcnZhdGlvbnMgdGhhdCBhcHBlYXIgaW4gYXQgbGVhc3Qgb25lIG9mIHRoZSB0YWJsZXMuIFRoZXJlIGFyZSB0aHJlZSB0eXBlcyBvZiBvdXRlciBqb2luczoKCiogQSAqKmxlZnQgam9pbioqIGtlZXBzIGFsbCBvYnNlcnZhdGlvbnMgaW4gYHhgLgoqIEEgKipyaWdodCBqb2luKioga2VlcHMgYWxsIG9ic2VydmF0aW9ucyBpbiBgeWAuCiogQSAqKmZ1bGwgam9pbioqIGtlZXBzIGFsbCBvYnNlcnZhdGlvbnMgaW4gYHhgIGFuZCBgeWAuCgo8aW1nIHNyYz0iLi4vcG5nL2pvaW4tb3V0ZXIucG5nIiBhbGlnbj0iY2VudGVyIiB3aWR0aD0iNDUwcHgiPgoKVGhlIG1vc3QgY29tbW9ubHkgdXNlZCBqb2luIGlzIHRoZSBsZWZ0IGpvaW46IHlvdSB1c2UgdGhpcyB3aGVuZXZlciB5b3UgbG9vayB1cCBhZGRpdGlvbmFsIGRhdGEgZnJvbSBhbm90aGVyIHRhYmxlLCBiZWNhdXNlIGl0IHByZXNlcnZlcyB0aGUgb3JpZ2luYWwgb2JzZXJ2YXRpb25zIGV2ZW4gd2hlbiB0aGVyZSBpc24ndCBhIG1hdGNoLiBUaGUgbGVmdCBqb2luIHNob3VsZCBiZSB5b3VyIGRlZmF1bHQgam9pbjogdXNlIGl0IHVubGVzcyB5b3UgaGF2ZSBhIHN0cm9uZyByZWFzb24gdG8gcHJlZmVyIG9uZSBvZiB0aGUgb3RoZXJzLgoKQW5vdGhlciB3YXkgdG8gdmlzdWFsbHkgcmVwcmVzZW50IHRoZXNlIGpvaW5zOgoKPGltZyBzcmM9Ii4uL3BuZy9qb2luLWRpYWdyYW0ucG5nIiBhbGlnbj0iY2VudGVyIiB3aWR0aD0iNjAwcHgiPgoKYGBge3J9CiMgTGVmdCBqb2luCnggJT4lIAogIGxlZnRfam9pbih5LCBieSA9ICJrZXkiKQpgYGAKCmBgYHtyfQojIFJpZ2h0IGpvaW4KeCAlPiUgCiAgcmlnaHRfam9pbih5LCBieSA9ICJrZXkiKQpgYGAKCmBgYHtyfQojIEZ1bGwgam9pbgp4ICU+JSAKICBmdWxsX2pvaW4oeSwgYnkgPSAia2V5IikKYGBgCgpTUUwgaXMgdGhlIGluc3BpcmF0aW9uIGZvciBkcGx5cidzIGNvbnZlbnRpb25zLCBzbyB0aGUgdHJhbnNsYXRpb24gaXMgc3RyYWlnaHRmb3J3YXJkOgoKfCBkcGx5ciAgICAgICAgICAgICAgICAgICAgICB8IFNRTCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCA6LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSB8IDotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIHwKfCBpbm5lcl9qb2luKHgsIHksIGJ5ID0gInoiKSB8IFNFTEVDVCAqIEZST00geCBJTk5FUiBKT0lOIHkgVVNJTkcgKHopICAgICAgIHwKfCBsZWZ0X2pvaW4oeCwgeSwgYnkgPSAieiIpICB8IFNFTEVDVCAqIEZST00geCBMRUZUIE9VVEVSIEpPSU4geSBVU0lORyAoeikgIHwKfCByaWdodF9qb2luKHgsIHksIGJ5ID0gInoiKSB8IFNFTEVDVCAqIEZST00geCBSSUdIVCBPVVRFUiBKT0lOIHkgVVNJTkcgKHopIHwKfCBmdWxsX2pvaW4oeCwgeSwgYnkgPSAieiIpICB8IFNFTEVDVCAqIEZST00geCBGVUxMIE9VVEVSIEpPSU4geSBVU0lORyAoeikgIHwJCgpCYXNlIFIgYWxzbyBwcm92aWRlcyBqb2luIGZ1bmN0aW9uYWxpdHkgYnkgYG1lcmdlKClgIGZ1bmN0aW9uLCBidXQgdXNpbmcgZHBseXIgam9pbmluZyBpcyBtdWNoIGZhc3RlciwgbW9yZSBjb25zaXN0ZW50IGFuZCB1bmxpa2UgYG1lcmdlKClgIGRwbHlyIGRvZXNuJ3QgY2hhbmdlIHRoZSBvcmRlciBvZiB0aGUgcm93cyBhZnRlciB0aGUgam9pbi4KCioqSGFuZGxpbmcgZHVwbGljYXRlIGtleXM6KioKCmBgYHtyfQp4IDwtIHRyaWJibGUoCiAgfmtleSwgfnZhbF94LAogICAgIDEsICJ4MSIsCiAgICAgMiwgIngyIiwKICAgICAyLCAieDMiLAogICAgIDMsICJ4NCIKKQp5IDwtIHRyaWJibGUoCiAgfmtleSwgfnZhbF95LAogICAgIDEsICJ5MSIsCiAgICAgMiwgInkyIiwKICAgICAyLCAieTMiLAogICAgIDMsICJ5NCIKKQpsZWZ0X2pvaW4oeCwgeSwgYnkgPSAia2V5IikKYGBgCgojIyMjIERlZmluaW5nIHRoZSBrZXkgY29sdW1ucwpUaGUgZGVmYXVsdCwgYGJ5ID0gTlVMTGAsIHVzZXMgYWxsIHZhcmlhYmxlcyB0aGF0IGFwcGVhciBpbiBib3RoIHRhYmxlcywgdGhlIHNvIGNhbGxlZCBuYXR1cmFsIGpvaW4uIEZvciBleGFtcGxlLCB0aGUgYGZsaWdodHNgIGFuZCBgd2VhdGhlcmAgdGFibGVzIG1hdGNoIG9uIHRoZWlyIGNvbW1vbiB2YXJpYWJsZXM6IGB5ZWFyYCwgYG1vbnRoYCwgYGRheWAsIGBob3VyYCBhbmQgYG9yaWdpbmA6CgpgYGB7cn0KZmxpZ2h0cyAlPiUgCiAgbGVmdF9qb2luKHdlYXRoZXIpICU+JQogIHByaW50CmBgYAoKV2UgY2FuIGFsc28gc3BlY2lmeSB3aGF0IGtleSB0byB1c2U6CgpgYGB7cn0KZmxpZ2h0cyAlPiUgCiAgbGVmdF9qb2luKHBsYW5lcywgYnkgPSAidGFpbG51bSIpICU+JQogIHByaW50CmBgYAoKYGJ5ID0gYygiYSIgPSAiYiIpYC4gVGhpcyB3aWxsIG1hdGNoIHZhcmlhYmxlIGBhYCBpbiB0YWJsZSBgeGAgdG8gdmFyaWFibGUgYGJgIGluIHRhYmxlIGB5YC4gVGhlIHZhcmlhYmxlcyBmcm9tIGB4YCB3aWxsIGJlIHVzZWQgaW4gdGhlIG91dHB1dDoKCmBgYHtyfQpmbGlnaHRzICU+JSAKICBsZWZ0X2pvaW4oYWlycG9ydHMsIGJ5ID0gYygiZGVzdCIgPSAiZmFhIikpICU+JQogIHByaW50CmBgYAoKLS0tCgojIyMgRmlsdGVyaW5nIEpvaW5zIApGaWx0ZXJpbmcgam9pbnMgbWF0Y2ggb2JzZXJ2YXRpb25zIGluIHRoZSBzYW1lIHdheSBhcyBtdXRhdGluZyBqb2lucywgYnV0IGFmZmVjdCB0aGUgb2JzZXJ2YXRpb25zLCBub3QgdGhlIHZhcmlhYmxlcy4gVGhlcmUgYXJlIHR3byB0eXBlczoKCiogYHNlbWlfam9pbih4LCB5KWAgKiprZWVwcyoqIGFsbCBvYnNlcnZhdGlvbnMgaW4gYHhgIHRoYXQgaGF2ZSBhIG1hdGNoIGluIGB5YC4KKiBgYW50aV9qb2luKHgsIHkpYCAqKmRyb3BzKiogYWxsIG9ic2VydmF0aW9ucyBpbiBgeGAgdGhhdCBoYXZlIGEgbWF0Y2ggaW4gYHlgLgoKU2VtaS1qb2lucyBhcmUgdXNlZnVsIGZvciBtYXRjaGluZyBmaWx0ZXJlZCBzdW1tYXJ5IHRhYmxlcyBiYWNrIHRvIHRoZSBvcmlnaW5hbCByb3dzLiBGb3IgZXhhbXBsZToKCmBgYHtyfQojIFRvcCAxMCBtb3N0IGZhdm9yaXRlIGRlc3RpbmF0aW9ucwp0b3BfZGVzdCA8LSBmbGlnaHRzICU+JQogIGNvdW50KGRlc3QsIHNvcnQgPSBUUlVFKSAlPiUKICBoZWFkKDEwKQpgYGAKClRoZSBmb2xsb3dpbmcgZ2l2ZXMgYWxsIHRoZSBmbGlnaHQgcmVjb3JkcyB0byB0aGUgdG9wIDEwIGRlc3RpbmF0aW9uczoKCmBgYHtyfQpmbGlnaHRzICU+JSAKICBzZW1pX2pvaW4odG9wX2Rlc3QpICU+JQogIHByaW50CmBgYAoKR3JhcGhpY2FsbHksIGEgc2VtaS1qb2luIGxvb2tzIGxpa2UgdGhpczoKCjxpbWcgc3JjPSIuLi9wbmcvc2VtaS1qb2luLnBuZyIgYWxpZ249ImNlbnRlciIgd2lkdGg9IjQ1MHB4Ij4KCk9ubHkgdGhlIGV4aXN0ZW5jZSBvZiBhIG1hdGNoIGlzIGltcG9ydGFudDsgaXQgZG9lc24ndCBtYXR0ZXIgd2hpY2ggb2JzZXJ2YXRpb24gaXMgbWF0Y2hlZC4gVGhpcyBtZWFucyB0aGF0IGZpbHRlcmluZyBqb2lucyBuZXZlciBkdXBsaWNhdGUgcm93cyBsaWtlIG11dGF0aW5nIGpvaW5zIGRvLgoKVGhlIGludmVyc2Ugb2YgYSBzZW1pLWpvaW4gaXMgYW4gYW50aS1qb2luLiBBbiBhbnRpLWpvaW4ga2VlcHMgdGhlIHJvd3MgdGhhdCBkb24ndCBoYXZlIGEgbWF0Y2g6Cgo8aW1nIHNyYz0iLi4vcG5nL2FudGktam9pbi5wbmciIGFsaWduPSJjZW50ZXIiIHdpZHRoPSI0NTBweCI+CgpBbnRpLWpvaW5zIGFyZSB1c2VmdWwgZm9yIGRpYWdub3Npbmcgam9pbiBtaXNtYXRjaGVzLiBGb3IgZXhhbXBsZSwgd2hlbiBjb25uZWN0aW5nIGZsaWdodHMgYW5kIHBsYW5lcywgeW91IG1pZ2h0IGJlIGludGVyZXN0ZWQgdG8ga25vdyB0aGF0IHRoZXJlIGFyZSBtYW55IGZsaWdodHMgdGhhdCBkb24ndCBoYXZlIGEgbWF0Y2ggaW4gYHBsYW5lc2A6CgpgYGB7cn0KZmxpZ2h0cyAlPiUKICBhbnRpX2pvaW4ocGxhbmVzLCBieSA9ICJ0YWlsbnVtIikgJT4lCiAgY291bnQodGFpbG51bSwgc29ydCA9IFRSVUUpICU+JQogIHByaW50CmBgYAoKLS0tCgojIyMgSm9pbiBwcm9ibGVtcwpXZSBoYXZlIGJlZW4gd29ya2luZyB3aXRoIGNsZWFuIGRhdGEsIHRoaXMgaXMgb2Z0ZW4gbm90IHRoZSBjYXNlIGluIHJlYWwgd29ybGQuIEhhdmUgdGhlIGZvbGxvd2luZyBwb2ludHMgaW4gbWluZCB3aGVuIGpvaW5pbmcgZGF0YWZyYW1lcyB0byBhdm9pZCBtYWtpbmcgbWlzdGFrZXM6CgoxLiBTdGFydCBieSBpZGVudGlmeWluZyB0aGUgdmFyaWFibGVzIHRoYXQgZm9ybSB0aGUgcHJpbWFyeSBrZXkgaW4gZWFjaCB0YWJsZS4gWW91IHNob3VsZCB1c3VhbGx5IGRvIHRoaXMgYmFzZWQgb24geW91ciB1bmRlcnN0YW5kaW5nIG9mIHRoZSBkYXRhLCBub3QgZW1waXJpY2FsbHkgYnkgbG9va2luZyBmb3IgYSBjb21iaW5hdGlvbiBvZiB2YXJpYWJsZXMgdGhhdCBnaXZlIGEgdW5pcXVlIGlkZW50aWZpZXIuIElmIHlvdSBqdXN0IGxvb2sgZm9yIHZhcmlhYmxlcyB3aXRob3V0IHRoaW5raW5nIGFib3V0IHdoYXQgdGhleSBtZWFuLCB5b3UgbWlnaHQgZ2V0ICh1bilsdWNreSBhbmQgZmluZCBhIGNvbWJpbmF0aW9uIHRoYXQncyB1bmlxdWUgaW4geW91ciBjdXJyZW50IGRhdGEgYnV0IHRoZSByZWxhdGlvbnNoaXAgbWlnaHQgbm90IGJlIHRydWUgaW4gZ2VuZXJhbC4KICAgICogRm9yIGV4YW1wbGUsIHRoZSBhbHRpdHVkZSBhbmQgbG9uZ2l0dWRlIHVuaXF1ZWx5IGlkZW50aWZ5IGVhY2ggYWlycG9ydCwgYnV0IHRoZXkgYXJlIG5vdCBnb29kIGlkZW50aWZpZXJzIQoKCjIuIENoZWNrIHRoYXQgbm9uZSBvZiB0aGUgdmFyaWFibGVzIGluIHRoZSBwcmltYXJ5IGtleSBhcmUgbWlzc2luZy4gSWYgYSB2YWx1ZSBpcyBtaXNzaW5nIHRoZW4gaXQgY2FuJ3QgaWRlbnRpZnkgYW4gb2JzZXJ2YXRpb24hCgoKMy4gQ2hlY2sgdGhhdCB5b3VyIGZvcmVpZ24ga2V5cyBtYXRjaCBwcmltYXJ5IGtleXMgaW4gYW5vdGhlciB0YWJsZS4gVGhlIGJlc3Qgd2F5IHRvIGRvIHRoaXMgaXMgd2l0aCBhbiBgYW50aV9qb2luKClgLiBJdCdzIGNvbW1vbiBmb3Iga2V5cyBub3QgdG8gbWF0Y2ggYmVjYXVzZSBvZiBkYXRhIGVudHJ5IGVycm9ycy4gRml4aW5nIHRoZXNlIGlzIG9mdGVuIGEgbG90IG9mIHdvcmsuCgoKNC4gQmUgYXdhcmUgdGhhdCBzaW1wbHkgY2hlY2tpbmcgdGhlIG51bWJlciBvZiByb3dzIGJlZm9yZSBhbmQgYWZ0ZXIgdGhlIGpvaW4gaXMgbm90IHN1ZmZpY2llbnQgdG8gZW5zdXJlIHRoYXQgeW91ciBqb2luIGhhcyBnb25lIHNtb290aGx5LiBJZiB5b3UgaGF2ZSBhbiBpbm5lciBqb2luIHdpdGggZHVwbGljYXRlIGtleXMgaW4gYm90aCB0YWJsZXMsIHlvdSBtaWdodCBnZXQgdW5sdWNreSBhcyB0aGUgbnVtYmVyIG9mIGRyb3BwZWQgcm93cyBtaWdodCBleGFjdGx5IGVxdWFsIHRoZSBudW1iZXIgb2YgZHVwbGljYXRlZCByb3dzIQoKLS0tCgojIyMgU2V0IG9wZXJhdGlvbnMKQWxsIHRoZXNlIG9wZXJhdGlvbnMgd29yayB3aXRoIGEgY29tcGxldGUgcm93LCBjb21wYXJpbmcgdGhlIHZhbHVlcyBvZiBldmVyeSB2YXJpYWJsZS4gVGhlc2UgZXhwZWN0IHRoZSBgeGAgYW5kIGB5YCBpbnB1dHMgdG8gaGF2ZSB0aGUgc2FtZSB2YXJpYWJsZXMsIGFuZCB0cmVhdCB0aGUgb2JzZXJ2YXRpb25zIGxpa2Ugc2V0czoKCiogYGludGVyc2VjdCh4LCB5KWA6IHJldHVybiBvbmx5IG9ic2VydmF0aW9ucyBpbiBib3RoIGB4YCBhbmQgYHlgLgoqIGB1bmlvbih4LCB5KWA6IHJldHVybiB1bmlxdWUgb2JzZXJ2YXRpb25zIGluIGB4YCBhbmQgYHlgLgoqIGBzZXRkaWZmKHgsIHkpYDogcmV0dXJuIG9ic2VydmF0aW9ucyBpbiBgeGAsIGJ1dCBub3QgaW4gYHlgLgoKR2l2ZW4gdGhpcyBzaW1wbGUgZGF0YToKCmBgYHtyfQpkZjEgPC0gdHJpYmJsZSgKICB+eCwgfnksCiAgIDEsICAxLAogICAyLCAgMQopCmRmMiA8LSB0cmliYmxlKAogIH54LCB+eSwKICAgMSwgIDEsCiAgIDEsICAyCikKYGBgCgpUaGUgZm91ciBwb3NzaWJpbGl0aWVzIGFyZToKCmBgYHtyfQppbnRlcnNlY3QoZGYxLCBkZjIpCmBgYAoKbm90ZSB0aGF0IHVuaW9uIGF1dG9tYXRpY2FsbHkgZmlsdGVycyB0aGUgb3ZlcmxhcHBpbmcgcmVjb3JkcywgaGVuY2UgMyByb3dzIQoKYGBge3J9CnVuaW9uKGRmMSwgZGYyKQpgYGAKCmBgYHtyfQpzZXRkaWZmKGRmMSwgZGYyKQpgYGAKCmBgYHtyfQpzZXRkaWZmKGRmMiwgZGYxKQpgYGAK